package lock

import (
	"context"
	"strconv"

	v1 "code.byted.org/bytertc/whiteboard_server/biz/api/pb/v1"
	"code.byted.org/bytertc/whiteboard_server/biz/pkg/biz_error"
	"code.byted.org/bytertc/whiteboard_server/biz/service/action"
	"code.byted.org/bytertc/whiteboard_server/biz/service/action/base"
	"code.byted.org/gopkg/logs/v2/log"
	"github.com/pkg/errors"
	"google.golang.org/protobuf/proto"
)

func NewAction(ctx context.Context, baseAct base.Action, raw *v1.Action) (act action.Action, err error) {
	lAction := lockAction{
		Action:      baseAct,
		rawAction:   raw,
		successObjs: map[string]bool{},
		failObjs:    map[string]bool{},
	}
	lockDetail := &v1.LockDetail{}
	err = proto.Unmarshal(raw.Data, lockDetail)
	if err != nil {
		return nil, biz_error.DecodeError.Wrap(errors.WithStack(err))
	}
	lAction.objectIds = lockDetail.ObjIds
	lAction.objType = lockDetail.LockObjType
	if lAction.objType == v1.LockObjType_kBoard {
		lAction.objectIds = []string{strconv.FormatInt(int64(baseAct.BoardId), 10)}
	}

	switch raw.Type {
	case v1.ActionType_kLock:
		act = &lockObj{
			lockAction: lAction,
		}
	case v1.ActionType_kUnLock:
		act = &unlockObj{
			lockAction: lAction,
		}
	default:
		return nil, biz_error.DecodeError.Wrap(errors.Errorf("no such lock action %d", raw.Type))
	}
	log.V1.CtxInfo(ctx, "lock action %s, %d", raw.Type, raw.Timestamp)
	return act, nil
}

type lockAction struct {
	base.Action
	rawAction    *v1.Action
	objectIds    []string
	successObjs  map[string]bool
	currentIndex int
	failObjs     map[string]bool
	objType      v1.LockObjType
}

func (l *lockAction) GetActionId() int32 {
	return l.rawAction.GetActionId()
}

func (l *lockAction) GetObjId() string {
	return l.objectIds[l.currentIndex]
}

func (l *lockAction) ConflictObjects() []string {
	var objects []string
	for obj := range l.failObjs {
		objects = append(objects, obj)
	}
	return objects
}
func (l *lockAction) GetOperator() string {
	return l.Operator
}

func (l *lockAction) IsAtomic() bool {
	return false
}

func (l *lockAction) Execute(ctx context.Context) error {
	return nil
}

// 不进行广播，所以返回nil
func (l *lockAction) RawActions() []*v1.Action {
	return []*v1.Action{}
}

type lockObj struct {
	lockAction
}

func (l *lockObj) Execute(ctx context.Context) error {
	for idx, obj := range l.objectIds {
		l.currentIndex = idx
		err := l.Repo.LockObject(ctx, l)
		if err != nil {
			if !errors.Is(err, biz_error.Conflict) {
				log.V1.CtxError(ctx, "fail to lock object %s, %+v", obj, err)
			}
			l.failObjs[obj] = true
		} else {
			l.successObjs[obj] = true
		}
	}
	if len(l.failObjs) > 0 {
		return biz_error.Conflict.Wrap(errors.Errorf("part obj lock fail:%+v", l.failObjs))
	}
	return nil
}

type unlockObj struct {
	lockAction
}

func (ul *unlockObj) Execute(ctx context.Context) error {
	for idx := range ul.objectIds {
		ul.currentIndex = idx
		err := ul.Repo.UnlockObject(ctx, ul)
		if err != nil {
			log.V1.CtxWarn(ctx, "fail to unlock object %s, %+v", ul.GetObjId(), err)
		}
	}
	return nil
}
